﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Reflection;
using System.Configuration;

namespace Muccu.JavaScript
{
    public class ProxyGenerator
    {
        private List<string> augments = new List<string>();
        private string applicationPath = string.Empty;
        private string serviceroot = ConfigurationManager.AppSettings["MuccuServiceRoot"] ?? "";

        public ProxyGenerator()
            : this("/")
        {
        }

        public ProxyGenerator(string appPath)
        {
            this.applicationPath = appPath + (appPath.EndsWith("/") ? "" : "/");
            Tracer.WriteTrace("Generator initialized for apppath: {0}", this.applicationPath);
        }

        public void Add(Type service)
        {
            Debug.Assert(service.BaseType.Name == "JavaScriptService");

            List<string> methods = new List<string>();

            var jsa = service.GetCustomAttributes(typeof(JavaScriptAttribute), false);
            var servicename = jsa.Length > 0 ? ((JavaScriptAttribute)jsa[0]).Name : service.Name;

            foreach (var mi in service.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
            {
                var attribs = mi.GetCustomAttributes(typeof(JavaScriptAttribute), false);
                var methodName = attribs.Length > 0 ? ((JavaScriptAttribute)attribs[0]).Name : mi.Name.ToCamelCase();
                var pInfos = mi.GetParameters();

                var argumentList = string.Join(", ", pInfos.Select(x => x.Name).ToArray());

                var validatorList = new string[pInfos.Length];
                for (var i = 0; i < pInfos.Length; i++)
                {
                    var argumentValidation = pInfos[i].GetCustomAttributes(typeof(ArgumentValidationAttribute), false).FirstOrDefault() as ArgumentValidationAttribute;
                    if (argumentValidation != null)
                        validatorList[i] = argumentValidation.ClientValidate;
                    else
                        validatorList[i] = GetGenericClientValidat(pInfos[i].ParameterType, i);
                }


                var cacheAttr = mi.GetCustomAttributes(typeof(CacheAttribute), false);
                var cacheDuration = (cacheAttr.Length == 1) ? ((CacheAttribute)cacheAttr[0]).Duration : 0;
                string part1 = string.Format("\t\t{0} : function({1}){{\n", methodName, argumentList);
                string part2 = string.Format("\t\t\treturn this.rpc('{0}{1}{2}.ashx', '{3}', [{4}], [{5}], {6});\n", applicationPath, serviceroot, servicename, methodName, argumentList, string.Join(", ", validatorList), cacheDuration);
                string part3 = string.Format("\t\t}}");
                methods.Add(part1 + part2 + part3);
            }

            augments.Add(string.Format("\tMuccu.prototype['{0}'] = $.extend({{\n", servicename) + string.Join(",\n", methods.ToArray()) + "\n}, Service);\n");
        }

        private string GetGenericClientValidat(Type parameterType, int idx)
        {
            switch (Type.GetTypeCode(parameterType))
            {
                case TypeCode.Boolean:
                    return "function(val, messages){var isValid = ($_toTypeString(val) == 'boolean');if(!isValid)messages.push('Argument #" + (idx + 1) + " should be of type boolean.'); return isValid;}";
                case TypeCode.Byte:
                case TypeCode.Decimal:
                case TypeCode.Double:
                case TypeCode.Int16:
                case TypeCode.Int32:
                case TypeCode.Int64:
                case TypeCode.SByte:
                case TypeCode.Single:
                case TypeCode.UInt16:
                case TypeCode.UInt32:
                case TypeCode.UInt64:
                    return "function(val, messages){var isValid = (!isNaN(val));if(!isValid)messages.push('Argument #" + (idx + 1) + " should be numeric.'); return isValid;}";
                case TypeCode.Char:
                case TypeCode.String:
                    return "function(val, messages){var isValid = ($_toTypeString(val) == 'string');if(!isValid)messages.push('Argument #" + (idx + 1) + " should be a string.'); return isValid;}";
                case TypeCode.DateTime:
                    return "function(val, messages){var isValid = ($_toTypeString(val) == 'date');if(!isValid)messages.push('Argument #" + (idx + 1) + " should be a date.'); return isValid;}";
                case TypeCode.Object:
                    return "function(val, messages){var isValid = ($_toTypeString(val) == 'object');if(!isValid)messages.push('Argument #" + (idx + 1) + " should be an object.'); return isValid;}";
                default:
                    return "undefined";
            }
        }

        public void WriteScript(TextWriter writer)
        {
#if !DEBUG
            writer = new MinifyingTextWriter(writer);
#endif

            writer.Write("(function(){\n");
            var assembly = Assembly.GetExecutingAssembly();

            using (var stream = new StreamReader(assembly.GetManifestResourceStream("Muccu.Resources.muccuCore.js")))
                writer.Write(stream.ReadToEnd());

            augments.ForEach(a => writer.Write(a));
            writer.Write(@"if (typeof define === 'function' && define.amd) {
    define('muccu', [], function () {
        return new Muccu();
    });
}
else {
    root.muccu = new Muccu();
}");
            writer.Write("}).call(this);\n");
        }
    }
}
